﻿using System;
using System.Collections.Generic;
using NHibernate;
using c = NHibernate.Cfg;
using NHibernate.Criterion;

namespace OrmSmackdown.NHibernate
{
    public class Exerciser : IExerciser
    {
        ISessionFactory _sessionFactory;
        ISession _session;

        public Exerciser()
        {
            c.Configuration _configuration = new c.Configuration();
            _configuration.AddAssembly("OrmSmackdown");
            _sessionFactory = _configuration.BuildSessionFactory();
            _session = _sessionFactory.OpenSession();
        }

        #region Insert
        public void Insert()
        {
            using (var session = _sessionFactory.OpenSession())
            {
                session.BeginTransaction();

                var adminRole = new NHibernate.Role { RoleName = Constants.ManagerRole, IsForPortal = true, IsForContact = false };
                var userRole = new NHibernate.Role { RoleName = Constants.EmployeeRole, IsForPortal = true, IsForContact = true };
                var contactRole = new NHibernate.Role { RoleName = Constants.ContactRole, IsForPortal = false, IsForContact = true };

                var user1 = new NHibernate.User { FirstName = Constants.User1FirstName, LastName = Constants.User1LastName, Email = Constants.User1Email, Role = adminRole };
                var user2 = new NHibernate.User { FirstName = Constants.User2FirstName, LastName = Constants.User2LastName, Email = Constants.User2Email, Role = userRole, Manager = user1 };
                var user3 = new NHibernate.User { FirstName = Constants.User3FirstName, LastName = Constants.User3LastName, Email = Constants.User3Email, Role = adminRole, Manager = user1 };
                var user4 = new NHibernate.User { FirstName = Constants.User4FirstName, LastName = Constants.User4LastName, Email = Constants.User4Email, Role = userRole, Manager = user3 };
                var user5 = new NHibernate.User { FirstName = Constants.User5FirstName, LastName = Constants.User5LastName, Email = Constants.User5Email, Role = userRole, Manager = user3 };

                var company1 = new NHibernate.Company { CompanyName = Constants.Company1 };
                var company2 = new NHibernate.Company { CompanyName = Constants.Company2 };

                var contact1 = new NHibernate.Contact { FirstName = Constants.Contact1FirstName, LastName = Constants.Contact1LastName, Email = Constants.Contact1Email, Role = contactRole, Company = company1 };
                var contact2 = new NHibernate.Contact { FirstName = Constants.Contact2FirstName, LastName = Constants.Contact2LastName, Email = Constants.Contact2Email, Role = contactRole, Company = company1 };
                var contact3 = new NHibernate.Contact { FirstName = Constants.Contact3FirstName, LastName = Constants.Contact3LastName, Email = Constants.Contact3Email, Role = contactRole, Company = company2 };
                var contact4 = new NHibernate.Contact { FirstName = Constants.Contact4FirstName, LastName = Constants.Contact4LastName, Email = Constants.Contact4Email, Role = contactRole, Company = company2 };
                var contact5 = new NHibernate.Contact { FirstName = Constants.Contact5FirstName, LastName = Constants.Contact5LastName, Email = Constants.Contact5Email, Role = contactRole, Company = company2 };
                var contact6 = new NHibernate.Contact { FirstName = Constants.Contact6FirstName, LastName = Constants.Contact6LastName, Email = Constants.Contact6Email, Role = contactRole, Company = company2 };

                var portal1 = new NHibernate.Portal { PortalName = Constants.Portal1, AdminUser = user1 };
                var portal2 = new NHibernate.Portal { PortalName = Constants.Portal2, AdminUser = user2 };

                session.Save(adminRole);
                session.Save(userRole);
                session.Save(contactRole);

                session.Save(company1);
                session.Save(company2);

                session.Save(user1);
                session.Save(user2);
                session.Save(user3);
                session.Save(user4);
                session.Save(user5);

                session.Save(contact1);
                session.Save(contact2);
                session.Save(contact3);
                session.Save(contact4);
                session.Save(contact5);
                session.Save(contact6);

                session.Save(portal1);
                session.Save(portal2);

                user1.Portals.Add(portal1);

                user2.Portals.Add(portal1);
                user2.Portals.Add(portal2);

                user3.Portals.Add(portal1);
                user3.Portals.Add(portal2);

                user4.Portals.Add(portal2);
                user5.Portals.Add(portal2);

                AddAddress(session, user1, "1000 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", false);
                AddAddress(session, user2, "2000 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", false);
                AddAddress(session, user3, "3000 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", false);
                AddAddress(session, user4, "4000 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", false);
                AddAddress(session, user5, "5000 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", false);
                AddAddress(session, user1, "1100 Some St.", string.Empty, Constants.TownWeCareAbout, "ST", "12345", "1234", true);
                AddAddress(session, user2, "1200 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", true);
                AddAddress(session, user3, "1300 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", true);
                AddAddress(session, user4, "1400 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", true);
                AddAddress(session, user5, "1500 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", true);

                AddAddress(session, contact1, "7000 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", false);
                AddAddress(session, contact2, "7000 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", false);
                AddAddress(session, contact3, "7000 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", false);
                AddAddress(session, contact4, "7000 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", false);
                AddAddress(session, contact5, "7000 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", false);
                AddAddress(session, contact6, "7000 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", false);
                AddAddress(session, contact1, "8000 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", true);
                AddAddress(session, contact2, "8100 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", true);
                AddAddress(session, contact3, "8200 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", true);
                AddAddress(session, contact4, "8300 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", true);
                AddAddress(session, contact5, "8400 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", true);
                AddAddress(session, contact6, "8500 Some St.", string.Empty, Constants.SomeOtherTown, "ST", "12345", "1234", true);

                session.Transaction.Commit();
            }
        }

        private void AddAddress(ISession session, Person p, string line1, string line2, string city, string state, string zip, string zip4, bool isDefault)
        {
            var addr = new NHibernate.Address { Line1 = line1, Line2 = line2, City = city, State = state, ZipCode = zip, ZipCodePlusFour = zip4, Person = p, IsDefault = isDefault };            
            session.Save(addr);
        }
        #endregion

        public IList<string> GetPortalAdminEmails()
        {
            List<string> emails = new List<string>();

            var portals = _session.CreateCriteria(typeof(Portal)).List();

            ///Ehhh... on this one, fairly certain there is a better way.
            foreach (var portal in portals)
                emails.Add(((Portal)portal).AdminUser.Email);

            return emails;
        }

        public IList<string> GetContactEmailsForCompany(string companyName)
        {
            var emails = _session.CreateCriteria(typeof(Contact))
                .SetFetchMode("Company", FetchMode.Join)
                .CreateCriteria("Company").Add(Expression.Eq("CompanyName", companyName))
                .SetProjection(Projections.ProjectionList().Add(Projections.Property("Email"), "Email"));

            return emails.List<string>();
        }

        public int GetNumberOfPortals()
        {
            var query = _session
                .CreateCriteria(typeof(Portal))               
                .SetProjection(Projections.ProjectionList().Add(Projections.Count("PortalId")));

            return query.UniqueResult<Int32>();
        }

        public int GetNumberOfUsers()
        {
            var query = _session
                .CreateCriteria(typeof(User))
                .SetProjection(Projections.ProjectionList().Add(Projections.Count("PersonId")));

            return query.UniqueResult<Int32>();
        }

        public int GetNumberOfContacts()
        {
            var query = _session
                .CreateCriteria(typeof(Contact))
                .SetProjection(Projections.ProjectionList().Add(Projections.Count("PersonId")));

            return query.UniqueResult<Int32>();
        }

        public string GetDefaultCity(string email)
        {
            var query = _session
                .CreateCriteria(typeof(Address))                
                .Add(Expression.Eq("IsDefault",true))
                .SetFetchMode("Person", FetchMode.Join)
                .CreateCriteria("Person").Add(Expression.Eq("Email", email));
                
            return ((Address)query.List()[0]).City;
        }

        public string GetAdminForPortal(string portalName)
        {
            var portal = (Portal)_session
                .CreateCriteria(typeof(Portal))
                .Add(Expression.Eq("PortalName", portalName)).UniqueResult();

            return portal.AdminUser.Email;
        }

        public double GetAverageUsersPerPortal()
        {
            var portals = _session.CreateQuery("from Portal").List();

            //Ehhh... on this one, fairly certain there is a better way, runs three
            //queries, yuk!
            int numbUsers = 0;
            foreach (var portal in portals)
                numbUsers += ((Portal)portal).PortalUsers.Count;

            return (double)numbUsers / (double)portals.Count;
        }

        public IList<string> GetUserEmails()
        {
            return _session
                .CreateCriteria(typeof(User))
                .SetProjection(Projections.ProjectionList().Add(Projections.Property("Email"), "Email"))
                .List<string>();
        }

        public IList<string> GetContactEmails()
        {
            return _session.CreateCriteria(typeof(Contact))
                .SetProjection(Projections.ProjectionList().Add(Projections.Property("Email"), "Email"))
                .List<string>();
        }

        public void Dispose()
        {
            _session.Dispose();
            _sessionFactory.Dispose();
        }
    }
}
